﻿using System;
using System.Collections.Generic;
using System.Linq;
using Gumini.JulaDB.Data;
using Gumini.JulaDB.DataTypes;

namespace Gumini.JulaDB.Planner
{
    /// <summary>
    /// Represents a sorting operation (ORDER BY).
    /// </summary>
    internal class OrderByNode : UnaryPlanNode
    {
        readonly IEnumerable<OrderItem> _orderItems;
        readonly RowComparer _comparer;

        public OrderByNode(PlanNode child, IEnumerable<OrderItem> orderItems)
            : base(child)
        {
            _orderItems = orderItems;
            var heading = child.Heading;
            var ordinals = orderItems.Select(n => Tuple.Create(heading.GetColumnOrdinal(n.ColumnName), n.Direction)).ToList();
            var dataTypes = heading.Select(n => n.Type).ToList();
            _comparer = new RowComparer(ordinals, dataTypes);
        }

        public override List<Row> Execute()
        {
            var result = base.Child.Rows;
            result.Sort(_comparer);
            return result;
        }

        private class RowComparer : IComparer<Row>
        {
            readonly List<Tuple<int, SortDirection>> _columnOrdinals;
            readonly List<DataType> _dataTypes;

            public RowComparer(List<Tuple<int, SortDirection>> columnOrdinals, List<DataType> dataTypes)
            {
                _columnOrdinals = columnOrdinals;
                _dataTypes = dataTypes;
            }

            public int Compare(Row x, Row y)
            {
                foreach(var ordinal in _columnOrdinals)
                {
                    var type = _dataTypes[ordinal.Item1];
                    var itemX = x[ordinal.Item1];
                    var itemY = y[ordinal.Item1];
                    
                    // current implementation does not allow to specify NULLS FIRST or NULLS LAST
                    // we use NULLS LAST if the sort is ASC and NULLS FIRST if the sort is DESC
                    int compareResult;
                    if (itemX == null && itemY == null)
                        compareResult = 0;
                    else if (itemX == null)
                        compareResult = 1;
                    else if (itemY == null)
                        compareResult = -1;
                    else
                        compareResult = type.Compare(itemX, itemY);
                    if (ordinal.Item2 == SortDirection.Descending)
                        compareResult = -compareResult;
                    if (compareResult != 0) // items are different
                        return compareResult;
                }
                return 0; // at this point elements are equal, so 0 is returned
            }
        }

        protected override string GetNodeDescription()
        {
            return String.Format("SORT ({0})",
                String.Join(", ", _orderItems.Select(i => String.Format("{0} {1}", i.ColumnName, i.Direction))));
        }
    }
}
